package com.y2.sparrow.generator;

import java.io.File;
import java.io.FileFilter;
import java.util.*;
import java.util.function.Function;

/**
 * 目录树生成器
 * <p>
 * {@linkplain #instance(String) 创建 DirectoryTreeGenerator 实例}
 * {@linkplain #deep(int) 设置查询文件目录深度}
 * {@linkplain #fileFilter(FileFilter)} 自定义文件过滤器}
 * {@linkplain #sort(Comparator<File>)} 自定义文件排序规则}
 * {@linkplain #ignoreByFileName(Collection<String>)}  忽略指定文件名}
 * {@linkplain #notes(Map<String, String>)}  配置文件注释信息}
 * {@linkplain #showSize()} 展示文件大小内容}
 * {@linkplain #showModify() 展示文件修改时间内容}
 * {@linkplain #showPermission() 展示文件权限内容}
 * {@linkplain #addAppendContent(Function) 自定义展示文件内容信息}
 * {@linkplain #generate() 生成目录树}
 *
 * @author shaoyuyao
 * @date 2024/12/25 15:48
 */
public class DirectoryTreeGenerator {
    /**
     * 换行符
     */
    private static final String LINE_SEPARATOR = System.getProperty("line.separator");
    /**
     * 空字符串
     */
    private static final String EMPTY = "";
    /**
     * 树枝（文件名连接符）
     */
    private static final String BRANCH = "│ ", INTERMEDIATE = "├─", END = "└─";
    /**
     * 树结构间距（默认：3个空格）
     */
    private static final String SPACING = "   ";
    /**
     * 结果集-每行
     */
    private final List<TreeLine> treeLines = new ArrayList<>();
    /**
     * 结果集-每行最大字符数
     */
    private int maxWordNum = 0;
    /**
     * 查询文件目录深度（默认：1）
     */
    private int deep = 1;
    /**
     * 文件展示内容（默认：文件名）
     */
    private final AppendTo displayContent = new AppendTo(file -> " " + file.getName());
    /**
     * 文件过滤器
     */
    private final CombinedFileFilter fileFilter = new CombinedFileFilter();
    /**
     * 文件排序规则
     */
    private Comparator sortComparator;
    /**
     * 文件注释符号（默认：#）
     */
    private String noteWord = "#";
    /**
     * 文件注释信息
     */
    private final Map<String, FileNote> notes = new HashMap<>();
    /**
     * 文件路径
     */
    private String filePath;

    private DirectoryTreeGenerator(String filePath, Comparator<File> sortComparator, FileFilter fileFilter) {
        this.filePath = filePath;
        this.sortComparator = sortComparator;
        this.fileFilter.addFilter(fileFilter);
    }

    /**
     * 创建 DirectoryTreeGenerator 实例
     *
     * @return DirectoryTreeGenerator 实例
     */
    public static DirectoryTreeGenerator instance(String filePath) throws Exception {
        if (null == filePath || "".equals(filePath.trim())) {
            throw new Exception("filePath is null");
        }

        // 默认排序规则：目录优先
        Comparator<File> sortComparator = (File f1, File f2) -> {
            if (f1.isDirectory() && !f2.isDirectory()) {
                return -1;
            } else if (!f1.isDirectory() && f2.isDirectory()) {
                return 1;
            } else {
                return f1.getName().compareToIgnoreCase(f2.getName());
            }
        };

        // 默认过滤规则：过滤隐藏文件
        FileFilter fileFilter = pathname -> !pathname.isHidden();

        return new DirectoryTreeGenerator(filePath, sortComparator, fileFilter);
    }

    /**
     * 设置查询文件目录深度
     *
     * @param deep 查询文件目录深度
     * @return DirectoryTreeGenerator 实例
     */
    public DirectoryTreeGenerator deep(int deep) {
        this.deep = deep;
        return this;
    }

    /**
     * 自定义文件过滤器
     *
     * @param fileFilter 文件过滤器
     * @return DirectoryTreeGenerator 实例
     */
    public DirectoryTreeGenerator fileFilter(FileFilter fileFilter) {
        this.fileFilter.addFilter(fileFilter);
        return this;
    }

    /**
     * 自定义文件排序规则
     *
     * @param sortComparator 文件排序规则
     * @return DirectoryTreeGenerator 实例
     */
    public DirectoryTreeGenerator sort(final Comparator<File> sortComparator) {
        this.sortComparator = sortComparator;
        return this;
    }

    /**
     * 忽略指定文件名
     *
     * @param ignores 忽略文件名列表
     * @return DirectoryTreeGenerator 实例
     */
    public DirectoryTreeGenerator ignoreByFileName(Collection<String> ignores) {
        if (null != ignores && ignores.size() > 0) {
            fileFilter.addFilter(file -> !ignores.contains(file.getName()));
        }
        return this;
    }

    /**
     * 配置文件注释信息
     *
     * @param notes 文件注释信息
     * @return DirectoryTreeGenerator 实例
     */
    public DirectoryTreeGenerator notes(Map<String, String> notes) {
        if (null != notes && notes.size() > 0) {
            for (Map.Entry<String, String> entry : notes.entrySet()) {
                this.notes.put(entry.getKey(), new FileNote(entry.getValue(), null));
            }
        }

        return this;
    }

    /**
     * 展示文件大小
     *
     * @return DirectoryTreeGenerator 实例
     */
    public DirectoryTreeGenerator showSize() {
        this.displayContent.add(file -> ("[" + file.length() + "B" + "]"));
        return this;
    }

    /**
     * 展示文件修改时间
     *
     * @return DirectoryTreeGenerator 实例
     */
    public DirectoryTreeGenerator showModify() {
        this.displayContent.add(file -> ("[" + new Date(file.lastModified()) + "]"));
        return this;
    }

    /**
     * 展示文件权限
     *
     * @return DirectoryTreeGenerator 实例
     */
    public DirectoryTreeGenerator showPermission() {
        this.displayContent.add(
                file -> ("["
                        + (file.canRead() ? "r-" : "--")
                        + (file.canWrite() ? "w-" : "--")
                        + (file.canExecute() ? "x-" : "--")
                        + "]")
        );
        return this;
    }

    /**
     * 自定义添加读取 file 解析内容到输出内容
     *
     * @param appendContent 追加内容
     * @return DirectoryTreeGenerator 实例
     */
    public DirectoryTreeGenerator addAppendContent(Function<File, String> appendContent) {
        this.displayContent.add(appendContent);
        return this;
    }

    /**
     * 生成目录树
     *
     * @return 目录树文本
     */
    public final String generate() throws Exception {
        File file = new File(this.filePath);
        if (!file.exists()) {
            throw new Exception(file.getPath() + " not found!");
        }

        if (!file.isDirectory()) {
            throw new Exception(file.getPath() + " is not directory!");
        }

        // 递归生成目录树
        this.generateHandle(file, EMPTY, 0, true);

        // 构建目录树文本
        return buildTreeText();
    }

    /**
     * 递归生成目录树
     *
     * @param file   文件实例
     * @param prefix 展示前缀
     * @param deep   查询文件目录深度
     * @param isRoot 是否是跟目录
     */
    private void generateHandle(File file, String prefix, int deep, boolean isRoot) {
        List<File> files = isRoot ? Collections.singletonList(file) : this.fetchFiles(file);
        if (files.isEmpty()) {
            return;
        }

        deep++;
        final int length = files.size();

        for (int i = 0; i < length; i++) {
            final File f = files.get(i);

            final boolean isLast = (i >= length - 1);

            StringBuilder line = new StringBuilder();
            line.append(prefix).append(isLast ? END : INTERMEDIATE);
            this.appendDisplayContent(f, line);

            if (line.length() > this.maxWordNum) {
                this.maxWordNum = line.length();
            }

            this.treeLines.add(new TreeLine(deep, f.getName(), line.toString()));

            if (f.isDirectory() && deep <= this.deep) {
                this.generateHandle(f, prefix + (!(length == 1 || isLast) ? BRANCH : EMPTY) + SPACING, deep, false);
            }
        }
    }

    /**
     * 目录读取子文件
     *
     * @param file 文件实例
     * @return 子文件实例列表
     */
    private List<File> fetchFiles(File file) {
        final File[] files = file.listFiles(this.fileFilter);
        if (null == files || files.length == 0) {
            return Collections.emptyList();
        }

        List<File> fileList = Arrays.asList(files);
        // 排序
        if (null != sortComparator) {
            fileList.sort(this.sortComparator);
        }

        return fileList;
    }

    /**
     * 构建目录树文本
     *
     * @return 目录树文本
     */
    private String buildTreeText() {
        StringBuilder result = new StringBuilder();

        for (TreeLine treeLine : this.treeLines) {
            if (notes.size() > 0) {

                FileNote fileNote = this.notes.get(treeLine.getFileName());
                if (null != fileNote) {
                    int spaceNum = maxWordNum - treeLine.getWordNum() + 3;
                    String newText = treeLine.getText()
                            + new String(new char[spaceNum]).replace('\0', ' ')
                            + noteWord + " " + fileNote.getNote();

                    result.append(newText);
                    result.append(LINE_SEPARATOR);
                    continue;
                }
            }

            result.append(treeLine.getText());
            result.append(LINE_SEPARATOR);
        }

        return result.toString();
    }

    /**
     * 处理定义文件内容
     *
     * @param file 文件实例
     */
    private void appendDisplayContent(File file, StringBuilder line) {
        final List<Function<File, String>> appendContents = displayContent.appendContents;
        for (Function<File, String> to : appendContents) {
            line.append(to.apply(file));
        }
    }

    /**
     * 可累积展示 tree 中具体文件属性内容
     */
    private static class AppendTo {
        private final List<Function<File, String>> appendContents = new ArrayList<>();

        private AppendTo(Function<File, String> appendTo) {
            if (appendTo != null) {
                this.appendContents.add(appendTo);
            }
        }

        private void add(Function<File, String> to) {
            if (to != null) {
                this.appendContents.add(0, to);
            }
        }
    }

    /**
     * 组合文件过滤器，支持将多个文件过滤逻辑动态组合到一起
     */
    private static class CombinedFileFilter implements FileFilter {
        private final Set<FileFilter> filters = new HashSet<>();

        private void addFilter(FileFilter filter) {
            filters.add(filter);
        }

        @Override
        public boolean accept(java.io.File file) {
            for (FileFilter filter : filters) {
                if (!filter.accept(file)) {
                    return false;
                }
            }
            return true;
        }
    }

    /**
     * 目录树行信息
     */
    private static class TreeLine {
        /**
         * 字符数
         */
        private int wordNum;
        /**
         * 当前文件所处在文件目录深度
         */
        private int deep;
        /**
         * 文件名
         */
        private String fileName;
        /**
         * 文本
         */
        private String text;

        private TreeLine(int deep, String fileName, String text) {
            this.wordNum = text.length();
            this.deep = deep;
            this.fileName = fileName;
            this.text = text;
        }

        public int getWordNum() {
            return wordNum;
        }

        public int getDeep() {
            return deep;
        }

        public String getFileName() {
            return fileName;
        }

        public String getText() {
            return text;
        }
    }

    /**
     * 文件注释
     */
    private static class FileNote {
        /**
         * 文件注释
         */
        private String note;
        /**
         * 文件所在层级，从1开始，根目录为1
         */
        private Integer level;

        public FileNote(String note, Integer deep) {
            this.note = note;
            this.level = deep;
        }

        public String getNote() {
            return note;
        }

        public Integer getLevel() {
            return level;
        }
    }
}